1 /*
2  * Katie James
3  *
2048
4  * CSIS
304 JavaScript Project
5 */
6
7 //Using the p5.js third-party JavaScript library
8
9 //constant declarations

10 const
GRID_SIZE = 4;
11 const
CELL_SIZE = 100;
12
13 //variable declarations

14 var
canvas;
15 var
grid;
16 var
gameOver;
17 var
score;
18 var
gameWon;
19
20 //DOM

21 scoreContainer = document.getElementById(
"score");
22
23 /* automatically called
by third-party p5 */
24 function setup() {
25     canvas = createCanvas(GRID_SIZE * CELL_SIZE +
50, GRID_SIZE * CELL_SIZE + 50);
26     background(
187, 173, 160);
27     centerCanvas(canvas);
28     newGame();
29     noLoop();
30     updateGrid();
31 }

32
33 /* centers the game canvavs
on window */
34 function centerCanvas() {
35     
var x = (windowWidth - width) / 2;
36     
var y = (windowHeight - height) / 2 + 40;
37     canvas.position(x, y);
38 }

39
40 /* recenters the game canvas
if the window is resized */
41 function windowResized() {
42     centerCanvas();
43 }

44
45 /* creates a
new game with the starting game board */
46 function newGame() {
47     
//fill grid with empty values (aka 0)
48     grid =
new Array(GRID_SIZE * GRID_SIZE).fill(0);
49     gameOver =
false;
50     gameWon =
false;
51     score =
0;
52     
//add the two starting tiles
53     addRandomTile();
54     addRandomTile();
55 }

56
57 /* updates the game grid with the correct tiles/tile colors, displays the score, and stops
58    the game
if there are no moves left or the 2048 tile is reached */
59 function updateGrid() {
60     displayScore();
61     drawGrid();
62     
if (gameOver) {
63         displayGameOver();
64     }
65     
else if (gameWon) {
66         displayGameWon();
67     }
68 }

69
70 /* automatically called
when a key is pressed. Mmoves the tiles on the grid
71    respective to which arrow key
is pressed. If Enter is pressed when the game
72    
is over, a new game is started. */
73 function keyPressed() {
74     
if (!gameOver && !gameWon) {
75         
switch (keyCode) {
76             
case UP_ARROW:
77                 verticalSlide(keyCode);
78                 updateGrid();
79                 
break;
80             
case DOWN_ARROW:
81                 verticalSlide(keyCode);
82                 updateGrid();
83                 
break;
84             
case RIGHT_ARROW:
85                 horizontalSlide(keyCode);
86                 updateGrid();
87                 
break;
88             
case LEFT_ARROW:
89                 horizontalSlide(keyCode);
90                 updateGrid();
91                 
break;
92         }
93     }
94     
//if game won/over and enter key is pressed, refresh page to start new game
95     
else if (keyCode === ENTER) {
96         
if (gameWon) {
97             location.reload();
98         }
99         
else {
100             location.reload();
101         }
102     }
103 }

104
105 /* slides the tiles vertically (up or down) and combines tiles of the
106    same
value if they collide. */
107 function verticalSlide(direction) {
108     
var previousGrid = [];
109     
var column;
110     
var filler;
111
112     arrayCopy(grid, previousGrid);
113
114     
for (var i = 0; i < GRID_SIZE; i++) {
115         column = [];
116         
//get column
117         
for (var j = i; j < GRID_SIZE * GRID_SIZE; j += 4) {
118             column.push(grid[j]);
119         }
120
121         
//combine like values in given vertical direction
122         column = combine(column, direction);
123
124         
//remove all empty values
125         column = column.filter(notEmpty);
126
127         
//add the correct number of empty values after the nonempty values
128         filler =
new Array(GRID_SIZE - column.length).fill(0);
129         
if (direction === UP_ARROW) {
130             column = column.concat(filler);
131         }
132         
else {
133             column = filler.concat(column);
134         }
135
136         
//update the current column in the grid
137         
for (var k = 0; k < column.length; k++) {
138             grid[k * GRID_SIZE + i] = column[k];
139         }
140
141         
//combine values that were previously separated, but now adajacent
142         combine(column, direction);
143     }
144     checkSlide(previousGrid);
145 }

146
147 /* slides the tiles horizontally (left or right) and combines tiles of the
148    same
value if they collide. */
149 function horizontalSlide(direction) {
150     
var previousGrid = [];
151     
var row;
152     
var filler;
153
154     arrayCopy(grid, previousGrid);
155
156     
for (var i = 0; i < GRID_SIZE; i++) {
157         
//get row
158         row = grid.slice(i * GRID_SIZE, i * GRID_SIZE + GRID_SIZE);
159
160         
//combine like values in given horizontal direction
161         row = combine(row, direction);
162
163         
//remove all empty values
164         row = row.filter(notEmpty);
165
166         
//add the correct number of empty values after the nonempty values
167         filler =
new Array(GRID_SIZE - row.length).fill(0);
168         
if (direction === LEFT_ARROW) {
169             row = row.concat(filler);
170         }
171         
else {
172             row = filler.concat(row);
173         }
174
175         
//remove the current row from the grid and add the updated row
176         grid.splice(i * GRID_SIZE, GRID_SIZE);
177         grid.splice(i * GRID_SIZE,
0, ...row);
178     }
179     checkSlide(previousGrid);
180 }

181
182 /* checks that a title
is nonempty (value is not zero). Used to filter a row of tiles */
183 function notEmpty(x) {
184     
return x > 0;
185 }

186
187 /* checks, after a key
is pressed, if anything on the grid actually moved or if the
188    game
is over */
189 function checkSlide(previousGrid) {
190     
if (!(grid.every((x, i) => x === previousGrid[i]))) {
191         addRandomTile();
192     }
193     
if (!movesLeft()) {
194         gameOver =
true;
195     }
196 }

197
198 /* checks
if there are any moves to play. In other words, there is either an empty
199    tile or
if two adjacent tiles have the same value. */
200 function movesLeft() {
201     
var movesLeft = false;
202     
var flag = false;
203     
var currentTile;
204     
var right;
205     
var bottom;
206
207     
for (var i = 0; i < GRID_SIZE; i++) {
208         
for (var j = 0; j < GRID_SIZE; j++) {
209             
if (!flag) {
210                 currentTile = grid[i * GRID_SIZE + j];
211
212                 
//if grid still has empty spots, there are moves left
213                 
if (currentTile === 0) {
214                     movesLeft =
true;
215                     flag =
true;
216                 }
217                 
else {
218                     
if (j < GRID_SIZE - 1) {
219                         right = grid[i * GRID_SIZE + j +
1];
220                     }
221                     
else {
222                         right =
0;
223                     }
224                     
if (i < GRID_SIZE - 1) {
225                         bottom = grid[(i +
1) * GRID_SIZE + j];
226                     }
227                     
else {
228                         bottom =
0;
229                     }
230                     
//if a neighbor of the current tile has the same value, there are moves left
231                     
if (currentTile === right || currentTile === bottom) {
232                         movesLeft =
true;
233                         flag =
true;
234                     }
235                 }
236             }
237         }
238     }
239
240     
return movesLeft;
241 }

242
243 /* combines tiles of the same values together of a specific row
in a given direction */
244 function combine(row, direction) {
245     
switch (direction) {
246         
case DOWN_ARROW:
247             row = combineDownRight(row);
248             
break;
249         
case RIGHT_ARROW:
250             row = combineDownRight(row);
251             
break;
252         
case UP_ARROW:
253             row = combineUpLeft(row);
254             
break;
255         
case LEFT_ARROW:
256             row = combineUpLeft(row);
257             
break;
258     }
259
260     
return row;
261 }

262
263 /* combines tiles of the same
value togther downwards or to the right */
264 function combineDownRight(row) {
265     
var x;
266     
var y;
267
268     
for (var i = row.length - 1; i > 0; i--) {
269         
//get current and subseqent tiles
270         x = row[i];
271         index = i -
1;
272         y = row[index];
273
274         
//skip empty tiles until a nonempty tile or the beginning of the row is reached
275         
while (y === 0 && index > 0) {
276             y = row[index--];
277         }
278
279         
//if the adjacent tiles have equal value, combine and update score
280         
if (x === y && x !== 0) {
281             row[i] = x + y;
282             score += row[i];
283             row[index] =
0;
284             
if (row[i] === 2048) {
285                 gameWon =
true;
286             }
287         }
288     }
289
290     
return row;
291 }

292
293 /* combines tiles of the same
value together upwards or to the left */
294 function combineUpLeft(row) {
295     
var x;
296     
var y;
297
298     
for (var i = 0; i < row.length - 1; i++) {
299         
//get current and subsequent tiles
300         x = row[i];
301         index = i +
1;
302         y = row[index];
303
304         
//skip empty tiles until a nonempty tile or the end of the row is reached
305         
while (y === 0 && index < row.length - 1) {
306             y = row[index++];
307         }
308
309         
//if the adjacent tiles have equal value, combine and update score
310         
if (x === y && x !== 0) {
311             row[i] = x + y;
312             score += row[i];
313             row[index] =
0;
314             
if (row[i] === 2048) {
315                 gameWon =
true;
316             }
317         }
318     }
319
320     
return row;
321 }

322
323 /* adds a
2 or 4 tile to an empty spot in the grid randomly */
324 function addRandomTile() {
325     
var emptyTiles = [];
326     
var index;
327     
var newTile = [2, 4];
328
329     
//add the indices of all empty tiles to the emptyTiles array
330     grid.forEach(function(
value, index) {
331         
if (value === 0) {
332             emptyTiles.push(index);
333         }
334     });
335
336     
if (emptyTiles.length > 0) {
337         
//get the index of a random empty tile in the grid
338         index = emptyTiles[Math.floor(Math.random() * emptyTiles.length)];
339         
//set the value of that empty tile to 2 or 4, randomly chosen
340         grid[index] = newTile[Math.floor(Math.random() * newTile.length)];
341     }
342 }

343
344 /*----------------STYLE----------------*/
345 /* insert the score
in the score container */
346 function displayScore() {
347     scoreContainer.innerHTML = `${score}`;
348 }

349
350 /* displays the Game Over message */

351 function displayGameOver() {
352     displayText(
"Game Over!\nHit Enter to Play Again", color(119, 110, 101), 32, width / 2, height / 2);
353 }

354
355 /* displays the Game Won message */

356 function displayGameWon() {
357     displayText(
"You Win!\nHit Enter to Play Again", color(119, 110, 101), 32, width / 2, height / 2);
358 }

359
360 /* general function to display text
on the canvas */
361 function displayText(message, color, size, xpos, ypos) {
362     textAlign(CENTER);
363     textSize(size);
364     fill(color);
365     text(message, xpos, ypos);
366 }

367
368 /* draw and style the game grid */

369 function drawGrid() {
370     
var c;
371
372     
for (var i = 0; i < GRID_SIZE; i++) {
373         
for (var j = 0; j < GRID_SIZE; j++) {
374             
//color of tile depends on value of tile
375             
switch (grid[i * GRID_SIZE + j]) {
376                 
case 0:
377                     c = color(
"#CDC0B4");
378                     
break;
379                 
case 2:
380                     c = color(
"#EEE4DA");
381                     
break;
382                 
case 4:
383                     c = color(
"#EDE0C8");
384                     
break;
385                 
case 8:
386                     c = color(
"#B2DFDB");
387                     
break;
388                 
case 16:
389                     c = color(
"#80CBC4");
390                     
break;
391                 
case 32:
392                     c = color(
"#4DB6AC");
393                     
break;
394                 
case 64:
395                     c = color(
"#26A69A");
396                     
break;
397                 
case 128:
398                     c = color(
"#009688");
399                     
break;
400                 
case 256:
401                     c = color(
"#00897B");
402                     
break;
403                 
case 512:
404                     c = color(
"#00796B");
405                     
break;
406                 
case 1024:
407                     c = color(
"#00695C");
408                     
break;
409                 
case 2048:
410                     c = color(
"#004D40");
411                     
break;
412             }
413
414             
//fill tile
415             fill(c);
416             
//thickness of tile border
417             strokeWeight(
2);
418             
//color of tile border
419             stroke(c);
420             
//draw rectangle with rounded edges for each tile
421             rect(j * CELL_SIZE + j *
10 + 10, i * CELL_SIZE + i * 10 + 10, CELL_SIZE, CELL_SIZE, 5);
422
423             
if (grid[i * GRID_SIZE + j] !== 0) {
424                 displayText(`${grid[i * GRID_SIZE + j]}`,
425                 color(
255, 255, 255),
426                 
45,
427                 j * CELL_SIZE + j *
10 + 10 + CELL_SIZE / 2,
428                 i * CELL_SIZE + i *
10 + 20 + CELL_SIZE / 2);
429             }
430         }
431     }
432 }


Gõ tìm kiếm nhanh...